package com.easyfile.easyfilemaster.service;

import com.google.common.collect.Multimap;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Part;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Minio文件业务
 * @author chenpeman
 * @date 2022年05月08日 19:47
 */
@Service
@Slf4j
public class MinioService {
    @Autowired
    private MyMinioClient minioClient;

    @Value("${minio.bucket}")
    private String bucketName;

    @PostConstruct
    public void checkBucket(){
        log.info("检查Minio存储桶是否存在.....");
        boolean b = bucketExists(bucketName);
        if (!b){
            log.warn("配置的Minio存储桶不存在");
        }
        log.info("Minio存储桶已存在");
    }
    /**
     * 查看存储bucket是否存在
     * @param bucketName 存储bucket
     * @return boolean
     */
    public Boolean bucketExists(String bucketName) {
        Boolean found;
        try {
            found = minioClient.bucketExists(BucketExistsArgs
                    .builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return found;
    }

    /**
     * 生成临时访问Url
     *
     * @param uri      文件名
     * @param fileName 下载保存的文件名
     * @param timeUnit 时间单位
     * @param time     临时时间
     * @return the string
     */
    public String getPresignedUrl(String uri,String fileName,TimeUnit timeUnit,long time){
        String presignedUrl;
        try{
            HashMap<String, String> map = new HashMap<>();
            //设置为浏览器直接下载
            map.put("response-content-type","application/octet-stream");
            //自定义下载的文件名
            map.put("response-content-disposition","attachment; filename="+fileName);
            presignedUrl= minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs
                    .builder()
                    .bucket(bucketName)
                    //文件uri
                    .object(uri)
                    // http请求方式
                    .method(Method.GET)
                    //设置直接下载
                    .extraQueryParams(map)
                    // url过期时间
                    .expiry(5, TimeUnit.MINUTES)
                    .build());
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
        return presignedUrl;
    }

    /**
     * 创建存储bucket
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs
                    .builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 删除存储bucket
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs
                    .builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 文件上传
     * @param file 文件
     * @param bucketName 存储bucket
     * @return Boolean
     */
    public Boolean upload(MultipartFile file, String bucketName) {
        try {
            PutObjectArgs objectArgs = PutObjectArgs
                    .builder()
                    .bucket(bucketName)
                    .object(file.getOriginalFilename())
                    .stream(file.getInputStream(),file.getSize(),-1)
                    .contentType(file.getContentType())
                    .build();
            //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 文件下载
     * @param bucketName 存储bucket名称
     * @param fileName 文件名称
     * @param res response
     * @return Boolean
     */
    public void download(String bucketName, String fileName, HttpServletResponse res) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName)
                .object(fileName).build();
        try (GetObjectResponse response = minioClient.getObject(objectArgs)){
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
                while ((len=response.read(buf))!=-1){
                    os.write(buf,0,len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                res.setCharacterEncoding("utf-8");
                //设置强制下载不打开
                res.setContentType("application/force-download");
                res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
                try (ServletOutputStream stream = res.getOutputStream()){
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 批量删除文件对象
     * @param bucketName 存储bucket名称
     * @param objects 对象名称集合
     */
    public Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> objects) {
        List<DeleteObject> dos = objects.stream().map(DeleteObject::new).collect(Collectors.toList());
        return minioClient.removeObjects(RemoveObjectsArgs
                .builder()
                .bucket(bucketName)
                .objects(dos)
                .build());
    }

    /**
     * 获取带签名的临时上传元数据对象，前端可获取后，直接上传到Minio
     *
     * @param fileName   the file name
     * @return presigned post form data
     */
    @SneakyThrows
    public Map<String, String> getPresignedPostFormData(String fileName) {
        // 为存储桶创建一个上传策略，过期时间为7天
        PostPolicy policy = new PostPolicy(bucketName, ZonedDateTime.now().plusDays(1));
        // 设置一个参数key，值为上传对象的名称
        policy.addEqualsCondition("key", fileName);
        // 添加Content-Type，例如以"image/"开头，表示只能上传照片
        policy.addStartsWithCondition("Content-Type", MediaType.ALL_VALUE);
        // 设置上传文件的大小 64kiB to 10MiB.
        //policy.addContentLengthRangeCondition(64 * 1024, 10 * 1024 * 1024);
        return minioClient.getPresignedPostFormData(policy);
    }

    /**
     * GetObject接口用于获取某个文件（Object）。此操作需要对此Object具有读权限。
     *
     * @param ossFilePath Oss文件路径
     */
    @SneakyThrows
    public InputStream getObject(String ossFilePath) {
        return minioClient.getObject(
                GetObjectArgs.builder().bucket(bucketName).object(ossFilePath).build());
    }

    /**
     * 上传分片上传请求，返回uploadId
     *
     * @param region           the region
     * @param objectName       the object name
     * @param headers          the headers
     * @param extraQueryParams the extra query params
     * @return the create multipart upload response
     */
    @SneakyThrows
    public CreateMultipartUploadResponse uploadId(String region,
                                                  String objectName,
                                                  Multimap<String, String> headers,
                                                  Multimap<String, String> extraQueryParams){
        return minioClient.createMultipartUpload(bucketName, region, objectName, headers, extraQueryParams);
    }
    /**
     * 完成分片上传，执行合并文件
     *
     * @param region           区域
     * @param objectName       对象名
     * @param uploadId         上传ID
     * @param parts            分片
     * @param extraHeaders     额外消息头
     * @param extraQueryParams 额外查询参数
     */
    @SneakyThrows
    public ObjectWriteResponse completeMultipartUpload(String region,
                                                       String objectName,
                                                       String uploadId,
                                                       Part[] parts,
                                                       Multimap<String, String> extraHeaders,
                                                       Multimap<String, String> extraQueryParams) {
        return minioClient.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);
    }

    /**
     * 查询分片数据
     *
     * @param region           区域
     * @param objectName       对象名
     * @param uploadId         上传ID
     * @param extraHeaders     额外消息头
     * @param extraQueryParams 额外查询参数
     */
    @SneakyThrows
    public ListPartsResponse listMultipart(String region,
                                           String objectName,
                                           Integer maxParts,
                                           Integer partNumberMarker,
                                           String uploadId,
                                           Multimap<String, String> extraHeaders,
                                           Multimap<String, String> extraQueryParams){
        return minioClient.listMultipart(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);
    }

    /**
     * 返回临时带签名、过期时间一天、Get请求方式的访问URL
     *
     * @param ossFilePath Oss文件路径
     * @return
     */
    @SneakyThrows
    public String getPresignedObjectUrl(String ossFilePath,Map<String, String> queryParams) {
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(bucketName)
                        .object(ossFilePath)
                        .expiry(60 * 60 * 24)
                        .extraQueryParams(queryParams)
                        .build());
    }

}
